Pythonデコレーター 1:Python デコレーターの紹介
この資料を執筆時点で、彼の記事は13年前のものになりますが、未だに有益な資料です。
Python コードについては、Python3 で動作するように修正しています。
著者: Bruce Eckel
2008年10月18日
概要
この驚くべき機能は、ほとんど申し訳なさそうに、そしてそれほど便利ではないかもしれないという懸念とともに言語に登場しました。
私は、やがてこの機能がこの言語のより強力な機能の1つとして見られるようになるだろうと予測しています。問題は、私が見てきたデコレーターの紹介がどれもかなり混乱を招くものだったということです。
Decorators vs. Decoratorパターン
最初に理解していただきたいのは、デコレーター(Decorator) という言葉は、Design Patterns Book に掲載されているDecoratorパターンと完全に混同されることが懸念されていたため、多少の恐れを持って使われていたということです。一時は他の言葉も検討されましたが、デコレータ(Decorator) が定着したようです。 確かに、Pythonのデコレーターを使ってDecoratorパターンを実装することはできますが、それは極めて限定的な使い方です。Pythonのデコレーターは、マクロと同じように考えるのが一番だと思います。
マクロの歴史
マクロの歴史は長いですが、ほとんどの人はCのプリプロセッサのマクロを経験したことがあるでしょう。C言語のマクロの問題点は、(1)別の言語(C言語ではないcpp)であること、(2)動作が奇妙なことがあり、C言語の他の部分の動作と矛盾することが多いこと、でした。
JavaもC#もアノテーションを追加して、言語の要素にいくつかのことをできるようにしています。これらの言語には、(1)やりたいことをやるためには、膨大でどうしようもない輪をくぐらなければならないことがある、(2)これらのアノテーション機能は、これらの言語の束縛と規律(Martin Fowler は優しく指示(Directing) と言っている)の性質によって手を縛られている、という問題がある。 少し話は変わりますが、私も含めて多くのC++プログラマーは、C++のテンプレートの生成能力に注目し、その機能をマクロ的に利用してきました。
他にも多くの言語がマクロを取り入れていますが、よく知らない私があえて言うのは、PythonのデコレータはLispのマクロに似たパワーと可能性を持っているということです。
マクロの目的
ある言語におけるマクロの目的は、言語の要素を変更する方法を提供することだと言ってもいいと思います。Pythonではデコレーターがそうです。関数や、クラスデコレータの場合はクラス全体を変更します。このため、デコレーターはメタクラスの代わりに、よりシンプルな手段を提供しています。
ほとんどの言語の自己修正アプローチの主な失敗点は、制限が多すぎることと、別の言語を必要とすることです(私は、興味深いアノテーションを作成するために飛び越えなければならないすべての輪を持つJavaアノテーションが「別の言語」を構成すると言いたいです)。
PythonはFowlerの言う「実現可能な」言語のカテゴリーに入ります。もしあなたが修正を行いたいのであれば、なぜ別の言語や制限された言語を作るのでしょうか?なぜPython自体を使わないのでしょうか?これがPythonのデコレーターの役割です。
デコレーターで何ができるのか?
デコレーターは、関数やクラスにコードを注入したり修正したりすることができます。Javaのアスペクト指向プログラミング(AOP) に少し似ていませんか?しかし,それよりもはるかにシンプルで,結果的にはるかに強力なものです.例えば、関数の入口と出口で何かをしたいとしましょう(ある種のセキュリティ、トレース、ロックなど)。-- AOPの標準的な引数のすべてです)。デコレーターを使うと、次のようになります。 code: python
@entryExit
def func1():
print("inside func1()")
@entryExit
def func2():
print("inside func2()")
@は、デコレータを使用するための演算子です。
関数デコレーター
関数デコレーターは、関数定義が始まる前の行に配置することで、その関数定義に適用されます。たとえば、次のようになります。
code: pytohn
@MyDecorator
def aFunction():
print("Inside aFunction")
コンパイラがこのコードを通過すると、aFunction() がコンパイルされ、結果の関数オブジェクトがmyDecoratorコードに渡されます。myDecoratorコードは、元の aFunction() の代わりに関数のようなオブジェクトを生成するような処理を行います。
myDecorator のコードはどのようなものでしょうか?しかし、関数ではなくクラスをデコレーションの仕組みとして使う方が、デコレーターを理解するのに簡単だということがわかりました。加えて、より強力です。
デコレーターから返されるオブジェクトの唯一の制約は、それが関数として使用できることです。したがって、デコレーターとして使用するクラスは、__call__() を実装しなければなりません。
デコレーターは何をすべきか?まあ、何でもできますが、通常は元の関数コードがどこかで使われることになります。しかし、これは必須ではありません。
code: python
class myDecorator(object):
def __init__(self, f):
print("inside myDecorator.__init__()")
f() # 関数の定義が完了したことを証明する
def __call__(self):
print("inside myDecorator.__call__()")
@myDecorator
def aFunction():
print("inside aFunction()")
print("Finished decorating aFunction()")
aFunction()
このコードを実行すると、次の出力になります。
code: bash
inside myDecorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside myDecorator.__call__()
myDecoratorのコンストラクタが、関数のデコレートを行う時点で実行されていることに注目してください。f() は__init__() の中で呼び出せるので、デコレータが呼ばれる前に f() の生成が完了していることを示しています。デコレーターのコンストラクターは、デコレートされる関数オブジェクトを受け取ることにも注意してください。一般的には、コンストラクタで関数オブジェクトを受け取り、後で __call__() メソッドで使用します (クラスを使用する場合、装飾と呼び出しが2つの明確な段階であることが、この方法がより簡単で強力であると私が主張する理由です)。
デコレーションされた後に aFunction() が呼ばれると、全く違う動作をします。元のコードではなく、myDecorator.__call__() メソッドが呼ばれるのです。これは、デコレートを行うことで、元の関数オブジェクトがデコレートの結果に置き換わるからです。実際、デコレーターが追加される前は、同じことを実現するためには、もっとエレガントでない方法を取らなければなりませんでした。
code: python
def foo(): pass
foo = staticmethod(foo)
デコレーター演算子@を追加することで、同じ結果を得ることができます。
code: python
@staticmethod
def foo(): pass
これがデコレーターに反対された理由です。@は、「関数オブジェクトを別の関数に通し、その結果を元の関数に代入する」という意味の、ちょっとしたシンタックスシュガーに過ぎないからです。
デコレーターが大きな影響を与えると思う理由は、このちょっとしたシンタックスシュガーが、プログラミングに対する考え方を変えてしまうからです。実際、「コードを他のコードに適用する」(つまり、マクロ)という考え方を、言語構造として正式なものにすることで、主流の考え方になるのです。
ちょっと便利に
さて、最初の例に戻って実装してみましょう。ここでは、より典型的な方法として、装飾された関数内のコードを実際に使用してみましょう。
code: python
class entryExit(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("Entering", self.f.__name__)
self.f()
print("Exited", self.f.__name__)
@entryExit
def func1():
print("inside func1()")
@entryExit
def func2():
print("inside func2()")
func1()
func2()
これを実行すると、次の出力になります。
code: bash
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
デコレートされた関数では、呼び出しの周りに Entering と Exited のトレース文が表示されるようになったことがわかります。
コンストラクタでは、関数オブジェクトである引数を格納しています。呼び出しでは、関数の __name__ 属性を使ってその関数の名前を表示し、その後、その関数自体を呼び出しています。
関数をデコレーターとして使う
デコレーターの結果の唯一の制約は、呼び出し可能であることです。上の例では、元の関数を __call__() メソッドを持つクラスのオブジェクトで置き換えました。しかし、関数オブジェクトも呼び出し可能なので、クラスの代わりに関数を使って、先ほどの例を次のように書き換えることができます。
code: python
def entryExit(f):
def new_f():
print("Entering", f.__name__)
f()
print("Exited", f.__name__)
return new_f
@entryExit
def func1():
print("inside func1()")
@entryExit
def func2():
print("inside func2()")
func1()
func2()
print(func1.__name__)
これを実行すると、次の出力になります。
code: bash
ntering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
new_f
new_f() は、entryExit() のボディ内で定義されているので、entryExit() が呼ばれたときに生成されて返されます。new_f() は、実際のfの値を取得するため、クロージャであることに注意してください。
new_f() が定義されると、デコレーター機構がその結果をデコレートされた関数として割り当てることができるように、entryExit() から返されます。
print(func1.__name__) という行の出力は new_f となっていますが、これはデコレートの際に new_f() 関数が元の関数の代わりになったためです。これが問題になる場合は、デコレーター関数を返す前にその名前を変更することができます。
code: python
def entryExit(f):
def new_f():
print("Entering", f.__name__)
f()
print("Exited", f.__name__)
new_f.__name__ = f.__name__
return new_f
@entryExit
def func1():
print("inside func1()")
@entryExit
def func2():
print("inside func2()")
func1()
func2()
print(func1.__name__)
これを実行すると、次の出力になります。
code: bash
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
func1
関数に関する情報を動的に得ることができ、それらの関数に変更を加えることができるので、Pythonでは非常に強力です。
その他の例
基本を理解したところで、デコレータの例をいくつか見てみましょう。ここでは、関数ではなくクラスをデコレーターとして使用している例が多いことに注意してください。
この記事では意図的にデコレートされた関数の引数を扱わないようにしていますが、これについては次の記事で説明します。
訳注: コメント(Talk Back!) と RSS Feed のセクションは割愛しています。
ブロガーについて
https://gyazo.com/b61197ffc968b0ec1e14d040e2fefb74
Bruce Eckel (www.BruceEckel.com) は、Pythonでの開発支援とFlexでのユーザーインターフェースの提供を行っている。Thinking in Java (Prentice-Hall, 1998, 2nd edition, 2000, 3rd edition, 2003, 4th edition, 2005)、Hands-On Java Seminar CD ROM (Webサイトで入手可能)、Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003)、C++ Inside & Out (Osborne/McGraw-Hill 1993)などの著書があります。また、世界中で何百ものプレゼンテーションを行い、数多くの雑誌に150以上の記事を掲載しているほか、ANSI/ISO C++委員会の創設メンバーでもあり、カンファレンスでも定期的に講演を行っています。